
'use strict'

const http = require('http')
const fs  = require('fs')

const fsp = fs.promises

/**-------------解析body数据---------------- */
/**
 * Content-Disposition:form-data;name="file;filename="xxx"
 * Content-Type:text/plain
 * 
 * @param {string} headerData 
 */

function parseBodyHeaders(headerData){
    let hlist = headerData.split('\r\n').filter(p => p.length > 0)

    //解析content-disposition =>hlist[0]
    //按照; 切分数组，选出从第二个元素开始的数组
    let csplit = hlist[0].split('; ').slice(1)

    let filename
    let name
    for(let a of csplit) {
        if (a.indexOf('filename=') === 0) {
            filename = a.substring(a.indexOf('=')+2, a.length-1)

        }else{
            name = a.substring(a.indexOf('=')+2, a.length-1)

        }
    }

    let headers ={}
    let ind = 0
    let k
    for(let h of hlist) {
        ind = h.indexOf(':')
        k = h.substring(0,ind).toLowerCase()
        headers[k] = h.substring(ind+1).trim()

    }
    return{
        filename,
        name,
        headers,
        contentType:headers['content-type'] || ''
    }
}
function parseBody (bodyData,headers) {
    let ctype = headers['content-type']

    let bdy = ctype.substring(ctype.indexOf('=')+1)
    
    let crlf_bdy =`\r\n--${bdy}`

    let header_end_index = bodyData.indexOf('\r\n\r\n',crlf_bdy.length);

    let headerData = bodyData.toString('utf8',crlf_bdy.length,header_end_index);

    //解析文件头信息

    let hd = parseBodyHeaders(headerData)
    let file = {
        start:header_end_index + 4,
        end:bodyData.indexOf(crlf_bdy,header_end_index),
        ...hd

    }
    
    file.length = file.end - file.start
    return file
}
//解析扩展名
function parseExtName(filename){
    if(filename.length <2){
        return ''
    }
    let nsplit = filename.split('.').filter(p=>p.length>0)
    if(nsplit.length < 2) return ''
    return `.${nsplit[nsplit.length - 1]}`
}

/**-------------解析Body数据 - END-------------*/

let html = `
    <!DOCTYPE html>
    <html>
        <head>
            <title>First HTTP</title>
        </head>
        <body>
            你好
        </body>
    <html> `
    let routerTable ={
    GET: {
        '/':async(ctx)=>{
            response.setHeader('content-type', 'text/html; charset=utf-8')
            ctx.end(html)

        },
        '/test':async(ctx)=>{
            let status =parseInt(Math.random() * 200) +199
            ctx.response.statusCode = status
            ctx.end('test end')
        },
        '/upload':async(ctx)=>{
            let stm = fs.createReadStream('./upload.html')
            stm.pipe(ctx.response,{end:false})
            stm.on('end',()=>{
                ctx.end()
            })
        },
    },

    POST :{
        '/data':async(ctx)=>{
            let file = parseBody(ctx.rawBody,ctx.headers)
            console.log(file)
            if(!ctx.rawBody){
                ctx.end();
                return;
            }
                let fname = `${Date.now()}-${Math.random()}`+`${parseExtName(file.filename)}`;
                let fh = await fsp.open(fname,'w+')
                let fret = await fh.write(ctx.rawBody,file.start,file.length,0);
                //关闭打开文件
                fh.close();
                
                ctx.end(`${fret.bytesWritten}`)
        }
    }
}
http.createServer(async (request, response) => {
    
    let rm = routerTable[request.method]
    let usplit = request.url.split('?')

    let pathname = usplit[0]
    let querystring = usplit[1] || ''

    if(!rm || !rm[pathname]){
        response.statusCode = 404
        response.end('page not found')
        return
    }
    //ctx 是context 的缩写，以为请求上下文，就是一个打包了各种数据和方法的对象
    let ctx = {
        method:request.method,
        path:pathname,
        search:querystring,
        headers:request.headers,
        request:request,
        response:response,
        end:(data,encoding = 'utf8') =>{
            response.end(data,encoding)
        },
        write:(data,encoding = 'utf8')=>{
            response.write(data,encoding)
        },
        totalLength:0,
        rawBody:null,
        bufferList:[]
    }

    await new Promise((rv,rj)=>{
        if('PD'.indexOf(request.method[0]) >= 0){
            request.on('data',chunk => {
                ctx.bufferList.push(chunk)
                ctx.totalLength += chunk.length

            })

        } else{
            request.on('data',data=>{
                response.statusCode = 400
                response.end('bad request',()=>{
                    request.destroy()

                })

            })

        }
        let resolved = false;
        request.on('end',()=>{
            resolved = true;
            rv();

        })
        request.on('close',()=>{
            (!resolved) && rv();

        })
    })


    if (!response.writable || response.writableEnded) {
        return
    } 
    if(ctx.bufferList.length>0){
        ctx.rawBody = Buffer.concat(ctx.bufferList,ctx.totalLength)
        ctx.bufferList=[]
    }
    rm[pathname ](ctx)
})
  

.listen(3456)
/***
 * .jpg .jpeg  --> image/jpeg
 * .png -->image/png
 * .gif ---> image/gif
 * .ico ===>image/x-icon
 * .txt ->text/plain
 * .json ->text/json 或application/json
 * .xml  -->text/xml 或 application/xml
 */